/******************************************************************************
 * $Id: history.c,v 1.40 2004/09/22 01:14:22 jimb Exp $
 * @file
 * @author  Jim Brooks http://www.jimbrooks.org
 * @brief   Functions for tag history.
 ******************************************************************************/

#include "common.h"
#include "history.h"

#include "list.h"
#include "widgets.h"
#include "notebook.h"
#include "text.h"
#include "statusbar.h"
#include "handlers.h"
#include "tags.h"
#include "treefunc.h"

/******************************************************************************
 * Create the tag-history clist widget with scrollbars (initially unpopulated).
 * The caller is responsible for packing this widget.
 * @param   pWidgetScrolledHistory (OUT/GLOBAL)
 * @param   pWidgetClistHistory (OUT/GLOBAL)
 ******************************************************************************/
void
CreateHistoryClistWidget( void )
{
   CreateInvisibleTagsClistWidget( &pWidgetScrolledHistory,
                                   &pWidgetClistHistory,
                                   tagsColumnsLayout,
                                   (PFUNC)HistoryClistWidgetRowSelectCallback,
                                   (PFUNC)HistoryClistWidgetRowUnselectCallback );
   return;
}

/******************************************************************************
 * Callback for when a row is selected in the history clist widget.
 *
 * The purpose of this callback is to load the text widget
 * with the module associated with the selected tag.
 *
 * Will automatically select the corresponding row in the modules clist,
 * thus indirectly calling ModulesClistWidgetRowSelectCallback().
 *
 * @param   pWidget
 *          Pointer to clist widget.
 * @param   row
 *          Which row was selected.
 * @param   col
 *          Which column was selected.
 * @param   GdkEventButton
 *          (not used here?)
 * @param   pData
 *          (not used here, NULL is passed)
 ******************************************************************************/
void
HistoryClistWidgetRowSelectCallback( GtkWidget*      pWidget,
                                     gint            row,
                                     gint            col,
                                     GdkEventButton* pEvent,
                                     gpointer	     pData )
{
   tag_t*	pTag;
   gint         correspondingTagsRow;
   GtkWidget*	pWidgetTextActivePrev   = pWidgetTextActive;
   GSList*		pGslistCursorMatch;
   guint		idxCursor;

g_return_if_fail( row < GTK_CLIST(pWidgetClistHistory)->rows );

  /*
   * [ This code is an adaption from TagsClistWidgetRowSelectCallback(). ]
   * Record which row is being selected.
   */
   widgetHistoryClistSelectedRow = row;

  /*
   * If prepending a tag into the history, return now.
   */
   if ( prependingTagHistory )
      return;

  /*
   * Prepare to load the module containing the tag, and move the cursor over the tag.
   *
   * Obtain the pointer to the tag_t struct that is associated
   * with the selected row in the tags clist widget.
   */
   pTag = (tag_t*)gtk_clist_get_row_data( GTK_CLIST(pWidget), row );
g_return_if_fail( pTag );

  /*
   * Should the tagged line be shown in the main or aux text widget?
   */
   SwitchActiveTextWidget( DONT_SWITCH_UNLESS_AUX_TEXT_KEY );

  /*
   * Load the selected module (ie source file) into the text widget.
   * Move the cursor to the line that is tagged.
   */
   if ( LoadTextWidgetWithModule( pTag->pModuleName,
                                  pTag->lineInt ,//- 1,
                                  row,
                                  DONT_RELOAD )
        != FUNC_OK )
   {
     /*
      * Oops, unable to load the module.
      */
      PrintStatusbarForRow( _("Could not load module."), row );
      goto quit;
   }

  /*
   * Select the corresponding row in the tags clist, which in turn will select
   * the corresponding row in the modules clist.
   * (Ie this is the case when the user explicitly selects a history tag
   *  and the tags/modules clist rows should be updated.)
   * A tag that is a recorded cursor position might be selected,
   * in which case, it has no corresponding tag produced by Exuberant ctags.
   */
   correspondingTagsRow = gtk_clist_find_row_from_data( GTK_CLIST(pWidgetClistTags), (gpointer)pTag );
   if ( correspondingTagsRow != - 1 )
   {
     /*
      * Set a flag so that the the tags/modules clists won't affect history rows.
      */
      activeHistoryRowSelect = TRUE;
      SelectClistRow( pWidgetClistTags,
                      correspondingTagsRow );
      activeHistoryRowSelect = FALSE;
   }
   else
   {
     /*
      * There is no corresponding row in the tags clist,
      * so just unselect rows in the tags/modules clist.
      */
      UnselectClistRow( pWidgetClistTags,
                        &widgetTagsClistSelectedRow );
      UnselectClistRow( pWidgetClistModules,
                        &widgetModulesClistSelectedRow );
   }

  /*
   * Select the history notebook page.
   */
   if ( autoSelectNotebookPage
        && NotebookPageHistory() != gtk_notebook_get_current_page( GTK_NOTEBOOK(pWidgetNotebook) ) )
      gtk_notebook_set_page( GTK_NOTEBOOK(pWidgetNotebook),
                             NotebookPageHistory() );

  /*
   * Highlight the historical tag in the text widget.
   * Try to restore cursor position.
   */
   pGslistCursorMatch = g_slist_nth( pGslistHistoryCursor, row );
   if ( pGslistCursorMatch )
   {
      idxCursor = (guint) pGslistCursorMatch->data;
      if ( idxCursor > MAX_LINES ) idxCursor = -1;
   }
   else
      idxCursor = -1;

  /*
   * If the historical tag is non-synthetic then let HighlightTag()
   * highlight the entire tag by passing -1.
   */
   if (    strcmp( pTag->pName, _("(recorded position)")      ) != 0
        && strcmp( pTag->pName, _("(user-recorded position)") ) != 0 )
      idxCursor = -1;

   HighlightTag( pTag, idxCursor );

  /*
   * Clear statusbar if row was selected by user.
   */
   if ( ! rowSelectedByProgram )
      MostlyClearStatusbar();

   ScheduleUpdateFuncTree();

quit:
  /*
   * Restore the active text widget.
   */
   SetActiveTextWidget( pWidgetTextActive = pWidgetTextActivePrev );

   return;
}

/******************************************************************************
 * Callback for when a row is UNselected in the tags clist widget.
 ******************************************************************************/
void
HistoryClistWidgetRowUnselectCallback( GtkWidget*      pWidget,
                                       gint            row,
                                       gint            col,
                                       GdkEventButton* pEvent,
                                       gpointer        pData )
{
#if 0
  /*
   * This doesn't work as intended because when a new row is selected,
   * the unselect signal for the old row is sent AFTER the select signal
   * for the new row, thus causing an incorrect assignment with ROW_NONE.
   */
   widgetHistoryClistRows = ROW_NONE;
#endif

   return;
}

/*****************************************************************************
 * Prepend a tag into the tag-history clist.
 *****************************************************************************/
void
PrependTagHistory( tag_t* pTag )
{
  /*
   * Set a global var to tell the history row-select callback
   * that this function is active (comments at callback explain why.)
   */
   prependingTagHistory = TRUE;

  /*
   * Call generic function.
   */
   AddRowToAnyTagsClistWidget( pWidgetClistHistory,
                               pTag,
                               NULL, /* unused */
                               0 );  /* prepend */

  /*
   * Select the top row in the history clist (that has the prepended tag).
   * If the user selects Earlier Tag, then the second row will have the expected tag.
   */
   gtk_clist_select_row( GTK_CLIST(pWidgetClistHistory),
                         0, 0 );

  /*
   * Record the cursor position (into a list).
   */
   gint idxCursor = OffsetAtCursor();
   gchar * pText = ActiveTextWidgetContents( NULL );
   if ( NULL == pText )
        idxCursor = -1;
   pGslistHistoryCursor = g_slist_prepend( pGslistHistoryCursor,
                                           GINT_TO_POINTER( UTF8IndexToAsciiIndex( pText,
                                                                                   idxCursor)));


  /*
   * Done prepending tag history, clear global var.
   */
   prependingTagHistory = FALSE;
   return;
}

/*****************************************************************************
 * Undo previous PrependTagHistory().
 * Does not free tag_t struct (caller should do so).
 *****************************************************************************/
void
UndoPrependTagHistory( void )
{
g_return_if_fail( GTK_CLIST(pWidgetClistHistory)->rows >= 0 );

  /*
   * Unless there are zero rows, remove the top row in history clist,
   * and dec count of rows.
   */
   if ( GTK_CLIST(pWidgetClistHistory)->rows > 0 )
   {
      gtk_clist_remove( GTK_CLIST(pWidgetClistHistory),
                        0 );
   }

   return;
}

/*****************************************************************************
 * Move up/down one row in history clist.
 * "Earlier Tag" button causes movement (step down) to next lower row, v.v.
 *****************************************************************************/
void
StepHistoryList( gboolean stepDown )
{
   void StepHistoryList___( gboolean _stepDown );

  /*
   * For stability, serialize this function.
   */
   if ( busy_StepHistoryList ) { DebugMsg( "StepHistoryList() is busy." ); return; }
   busy_StepHistoryList = TRUE;
   StepHistoryList___( stepDown );
   busy_StepHistoryList = FALSE;
}
void
StepHistoryList___( gboolean stepDown )
{
  /*
   * Is history empty?
   */
   if ( GTK_CLIST(pWidgetClistHistory)->rows == 0 )
   {
      PrintStatusbar( _("History is empty.") );
      return;
   }

  /*
   * If a row is currently selected, determine if there is a higher/lower row.
   */
   if (    ! stepDown
        && widgetHistoryClistSelectedRow == 0 )
   {
      PrintStatusbar( _("No more later tags in history.") );
      return;
   }
   if (    stepDown
        && widgetHistoryClistSelectedRow == (GTK_CLIST(pWidgetClistHistory)->rows - 1) )
   {
      PrintStatusbar( "No more earlier tags in history." );
      return;
   }

  /*
   * If no row is selected then select latest tag in history, regardless.
   * Otherwise, inc/dec the row number.
   */
   if ( widgetHistoryClistSelectedRow == ROW_NONE )
   {
      widgetHistoryClistSelectedRow = 0;
   }
   else
   {
      if ( stepDown )
         ++widgetHistoryClistSelectedRow;
      else
         --widgetHistoryClistSelectedRow;
   }

  /*
   * Select the clist row.
   */
   SelectClistRowAsThoughUserDid( pWidgetClistHistory,
                                  widgetHistoryClistSelectedRow );

   return;
}

/*****************************************************************************
 * Record the current cursor position as a temporary tag in the tag history.
 *****************************************************************************/
void
RecordCursorInTagHistory( char* pTagName )
{
   tag_t* pTagCurrentPosition;
   tag_t* pTag;
   int line;

  /*
   * Is the text widget empty?
   */
   if ( IsTextWidgetEmpty() ) return;

  /*
   * Which line is cursor at?
   */
   line = LineAtCursor();
g_return_if_fail( line != -1 );
   if ( line == -1 ) return;

  /*
   * Create a tag_t struct to define the current position,
   * and prepend it into the tags history.
   */
   pTagCurrentPosition = AllocTagStruct();
   FillTagStruct( pTagCurrentPosition,
                  pTagName,
                  line,
                  "(N/A)",
                  ActiveModuleName() );

  /*
   * To avoid duplicates, don't record the saved position
   * if it is very near the recently added history tag.
   * Duplication can happen when clicking tags or tree items in succession.
   */
   pTag = (tag_t*)gtk_clist_get_row_data( GTK_CLIST(pWidgetClistHistory), 0 );
   if ( pTag )
   {
     /*
      * Compare module name and line #s (approx).
      */
      if (     abs( pTag->lineInt - pTagCurrentPosition->lineInt ) <= 2
           &&  CompareStrings( pTag->pModuleName, pTagCurrentPosition->pModuleName ) == 0 )
         return;
   }

   PrependTagHistory( pTagCurrentPosition );

  /*
   * (tag_t struct isn't freed.)
   */
   return;
}

/*****************************************************************************
 * Undo the previous RecordCursorInTagHistory().
 *****************************************************************************/
void
UndoRecordCursorInTagHistory( void )
{
   UndoPrependTagHistory();
}
